{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font color=\"red\">注</font>: 使用 tensorboard 可视化需要安装 tensorflow (TensorBoard依赖于tensorflow库，可以任意安装tensorflow的gpu/cpu版本)\n",
    "\n",
    "```shell\n",
    "pip install tensorflow-cpu\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T08:01:42.185013Z",
     "start_time": "2025-01-21T08:01:42.181175Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T08:17:44.804492Z",
     "iopub.status.busy": "2025-01-21T08:17:44.804328Z",
     "iopub.status.idle": "2025-01-21T08:17:46.958945Z",
     "shell.execute_reply": "2025-01-21T08:17:46.958384Z",
     "shell.execute_reply.started": "2025-01-21T08:17:44.804473Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=10, micro=14, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cu124\n",
      "cuda:0\n"
     ]
    }
   ],
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n",
    "\n",
    "seed = 42\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 数据准备\n",
    "\n",
    "https://www.kaggle.com/competitions/cifar-10/data\n",
    "\n",
    "```shell\n",
    "$ tree -L 1 cifar-10                                    \n",
    "cifar-10\n",
    "├── sampleSubmission.csv\n",
    "├── test\n",
    "├── train\n",
    "└── trainLabels.csv\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T08:01:43.186193Z",
     "start_time": "2025-01-21T08:01:42.198531Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T08:17:46.960544Z",
     "iopub.status.busy": "2025-01-21T08:17:46.960147Z",
     "iopub.status.idle": "2025-01-21T08:17:49.220895Z",
     "shell.execute_reply": "2025-01-21T08:17:49.220214Z",
     "shell.execute_reply.started": "2025-01-21T08:17:46.960522Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(PosixPath('competitions/cifar-10/train/1.png'), 'frog'),\n",
      " (PosixPath('competitions/cifar-10/train/2.png'), 'truck'),\n",
      " (PosixPath('competitions/cifar-10/train/3.png'), 'truck'),\n",
      " (PosixPath('competitions/cifar-10/train/4.png'), 'deer'),\n",
      " (PosixPath('competitions/cifar-10/train/5.png'), 'automobile')]\n",
      "[(PosixPath('competitions/cifar-10/test/1.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/2.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/3.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/4.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/5.png'), 'cat')]\n",
      "50000 300000\n"
     ]
    }
   ],
   "source": [
    "from pathlib import Path\n",
    "\n",
    "DATA_DIR = Path(\".\")\n",
    "DATA_DIR1 =Path(\"competitions/cifar-10/\")\n",
    "train_lables_file = DATA_DIR / \"trainLabels.csv\"\n",
    "test_csv_file = DATA_DIR / \"sampleSubmission.csv\" #测试集模板csv文件\n",
    "train_folder = DATA_DIR1 / \"train\"\n",
    "test_folder = DATA_DIR1 / \"test\"\n",
    "\n",
    "#所有的类别\n",
    "class_names = [\n",
    "    'airplane',\n",
    "    'automobile',\n",
    "    'bird',\n",
    "    'cat',\n",
    "    'deer',\n",
    "    'dog',\n",
    "    'frog',\n",
    "    'horse',\n",
    "    'ship',\n",
    "    'truck',\n",
    "]\n",
    "\n",
    "def parse_csv_file(filepath, folder):\n",
    "    \"\"\"Parses csv files into (filename(path), label) format\"\"\"\n",
    "    results = []\n",
    "    #读取所有行\n",
    "    with open(filepath, 'r') as f:\n",
    "#         lines = f.readlines()  为什么加[1:]，可以试这个\n",
    "        #第一行不需要，因为第一行是标签\n",
    "        lines = f.readlines()[1:] \n",
    "    for line in lines:#依次去取每一行\n",
    "        image_id, label_str = line.strip('\\n').split(',')\n",
    "        image_full_path = folder / f\"{image_id}.png\"\n",
    "        results.append((image_full_path, label_str)) #得到对应图片的路径和分类\n",
    "    return results\n",
    "\n",
    "#解析对应的文件夹\n",
    "train_labels_info = parse_csv_file(train_lables_file, train_folder)\n",
    "test_csv_info = parse_csv_file(test_csv_file, test_folder)\n",
    "#打印\n",
    "import pprint\n",
    "pprint.pprint(train_labels_info[0:5])\n",
    "pprint.pprint(test_csv_info[0:5])\n",
    "print(len(train_labels_info), len(test_csv_info))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T08:01:43.217782Z",
     "start_time": "2025-01-21T08:01:43.186193Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T08:17:49.221678Z",
     "iopub.status.busy": "2025-01-21T08:17:49.221499Z",
     "iopub.status.idle": "2025-01-21T08:17:49.278733Z",
     "shell.execute_reply": "2025-01-21T08:17:49.278233Z",
     "shell.execute_reply.started": "2025-01-21T08:17:49.221658Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                            filepath       class\n",
      "0  competitions/cifar-10/train/1.png        frog\n",
      "1  competitions/cifar-10/train/2.png       truck\n",
      "2  competitions/cifar-10/train/3.png       truck\n",
      "3  competitions/cifar-10/train/4.png        deer\n",
      "4  competitions/cifar-10/train/5.png  automobile\n",
      "                                filepath       class\n",
      "0  competitions/cifar-10/train/45001.png       horse\n",
      "1  competitions/cifar-10/train/45002.png  automobile\n",
      "2  competitions/cifar-10/train/45003.png        deer\n",
      "3  competitions/cifar-10/train/45004.png  automobile\n",
      "4  competitions/cifar-10/train/45005.png    airplane\n",
      "                           filepath class\n",
      "0  competitions/cifar-10/test/1.png   cat\n",
      "1  competitions/cifar-10/test/2.png   cat\n",
      "2  competitions/cifar-10/test/3.png   cat\n",
      "3  competitions/cifar-10/test/4.png   cat\n",
      "4  competitions/cifar-10/test/5.png   cat\n"
     ]
    }
   ],
   "source": [
    "# train_df = pd.DataFrame(train_labels_info)\n",
    "train_df = pd.DataFrame(train_labels_info[0:45000])\n",
    "valid_df = pd.DataFrame(train_labels_info[45000:])\n",
    "test_df = pd.DataFrame(test_csv_info)\n",
    "\n",
    "train_df.columns = ['filepath', 'class']\n",
    "valid_df.columns = ['filepath', 'class']\n",
    "test_df.columns = ['filepath', 'class']\n",
    "\n",
    "print(train_df.head())\n",
    "print(valid_df.head())\n",
    "print(test_df.head())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T08:01:44.158498Z",
     "start_time": "2025-01-21T08:01:43.217782Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T08:17:49.279531Z",
     "iopub.status.busy": "2025-01-21T08:17:49.279337Z",
     "iopub.status.idle": "2025-01-21T08:17:50.075246Z",
     "shell.execute_reply": "2025-01-21T08:17:50.074702Z",
     "shell.execute_reply.started": "2025-01-21T08:17:49.279511Z"
    }
   },
   "outputs": [],
   "source": [
    "from PIL import Image\n",
    "from torch.utils.data import Dataset, DataLoader\n",
    "from torchvision import transforms\n",
    "\n",
    "class Cifar10Dataset(Dataset):\n",
    "    df_map = {\n",
    "        \"train\": train_df,\n",
    "        \"eval\": valid_df,\n",
    "        \"test\": test_df\n",
    "    }\n",
    "    label_to_idx = {label: idx for idx, label in enumerate(class_names)}\n",
    "    idx_to_label = {idx: label for idx, label in enumerate(class_names)}\n",
    "    def __init__(self, mode, transform=None):\n",
    "        self.df = self.df_map.get(mode, None)\n",
    "        if self.df is None:\n",
    "            raise ValueError(\"mode should be one of train, val, test, but got {}\".format(mode))\n",
    "\n",
    "        self.transform = transform\n",
    "        \n",
    "    def __getitem__(self, index):\n",
    "        img_path, label = self.df.iloc[index]\n",
    "        img = Image.open(img_path).convert('RGB')\n",
    "        # # img 转换为 channel first\n",
    "        # img = img.transpose((2, 0, 1))\n",
    "        # transform\n",
    "        img = self.transform(img)\n",
    "        # label 转换为 idx\n",
    "        label = self.label_to_idx[label]\n",
    "        return img, label\n",
    "    \n",
    "    def __len__(self):\n",
    "        return self.df.shape[0]\n",
    "    \n",
    "IMAGE_SIZE = 32\n",
    "mean, std = [0.4914, 0.4822, 0.4465], [0.247, 0.243, 0.261]\n",
    "\n",
    "transforms_train = transforms.Compose([\n",
    "        # resize\n",
    "        transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),\n",
    "        # random rotation 40\n",
    "        transforms.RandomRotation(40),\n",
    "        # horizaontal flip\n",
    "        transforms.RandomHorizontalFlip(),\n",
    "        transforms.ToTensor(),\n",
    "        transforms.Normalize(mean, std)\n",
    "    ])\n",
    "\n",
    "transforms_eval = transforms.Compose([\n",
    "        # resize\n",
    "        transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),\n",
    "        transforms.ToTensor(),\n",
    "        transforms.Normalize(mean, std)\n",
    "    ])\n",
    "\n",
    "train_ds = Cifar10Dataset(\"train\", transforms_train)\n",
    "eval_ds = Cifar10Dataset(\"eval\", transforms_eval) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T08:01:44.162011Z",
     "start_time": "2025-01-21T08:01:44.158498Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T08:17:50.076347Z",
     "iopub.status.busy": "2025-01-21T08:17:50.075918Z",
     "iopub.status.idle": "2025-01-21T08:17:50.079491Z",
     "shell.execute_reply": "2025-01-21T08:17:50.079002Z",
     "shell.execute_reply.started": "2025-01-21T08:17:50.076324Z"
    }
   },
   "outputs": [],
   "source": [
    "batch_size = 64\n",
    "train_dl = DataLoader(train_ds, batch_size=batch_size, shuffle=True, num_workers=4)   \n",
    "eval_dl = DataLoader(eval_ds, batch_size=batch_size, shuffle=False, num_workers=4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T08:01:44.173034Z",
     "start_time": "2025-01-21T08:01:44.162011Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T08:17:50.080075Z",
     "iopub.status.busy": "2025-01-21T08:17:50.079910Z",
     "iopub.status.idle": "2025-01-21T08:17:50.082683Z",
     "shell.execute_reply": "2025-01-21T08:17:50.082228Z",
     "shell.execute_reply.started": "2025-01-21T08:17:50.080057Z"
    }
   },
   "outputs": [],
   "source": [
    "# 遍历train_ds得到每张图片，计算每个通道的均值和方差\n",
    "# def cal_mean_std(ds):\n",
    "#     mean = 0.\n",
    "#     std = 0.\n",
    "#     for img, _ in ds:\n",
    "#         mean += img.mean(dim=(1, 2))\n",
    "#         std += img.std(dim=(1, 2))\n",
    "#     mean /= len(ds)\n",
    "#     std /= len(ds)\n",
    "#     return mean, std\n",
    "#\n",
    "# # 经过 normalize 后 均值为0，方差为1\n",
    "# print(cal_mean_std(train_ds))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义模型"
   ]
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 在分组卷积中，输入图像被分成多个组，每组卷积一次，然后再将结果拼接起来。当进行池化后，图像尺寸会减半，因此分组卷积需要保证分组大小能够整除图像大小，即通过对池化后的图像大小进行调整，使得分组大小能够整除图像大小。，外围通常填充0，使得分组卷积后图像尺寸不变。"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# width_padding = (input_shape[-2] - max_pooling_shape[-2]) // 2 # // 输入尺寸，减去池化尺寸，再除以2，求的是左右填充的长度，同理求上下填充的长度。.pad()函数的padding参数为[left, right, top, bottom]，分别表示左右上下填充的长度。"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "#  output_channel_for_each_path: list[int]这个表示每个分支输出的通道数，第一个元素表示1*1卷积输出通道数，第二个元素表示3*3卷积输出通道数，第三个元素表示5*5卷积输出通道数，第四个元素表示池化层输出通道数。"
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T08:07:02.978303Z",
     "start_time": "2025-01-21T08:07:02.972820Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T08:17:50.084733Z",
     "iopub.status.busy": "2025-01-21T08:17:50.084474Z",
     "iopub.status.idle": "2025-01-21T08:17:50.091206Z",
     "shell.execute_reply": "2025-01-21T08:17:50.090722Z",
     "shell.execute_reply.started": "2025-01-21T08:17:50.084714Z"
    }
   },
   "outputs": [],
   "source": [
    "class InceptionBolck(nn.Module):\n",
    "    #output_channel_for_each_path: 1*1卷积输出通道数，3*3卷积输出通道数，5*5卷积输出通道数，池化层输出通道数\n",
    "    def __init__(self, input_channels: int, output_channel_for_each_path: list[int]):\n",
    "        super(InceptionBolck, self).__init__()\n",
    "        self.conv1_1 = nn.Conv2d(\n",
    "            in_channels=input_channels, \n",
    "            out_channels=output_channel_for_each_path[0],\n",
    "            kernel_size=1, \n",
    "            padding=\"same\"\n",
    "            )\n",
    "        \n",
    "        self.conv3_3 = nn.Conv2d(\n",
    "            in_channels=input_channels, \n",
    "            out_channels=output_channel_for_each_path[1], \n",
    "            kernel_size=3, \n",
    "            padding=\"same\"\n",
    "            )\n",
    "        self.conv5_5 = nn.Conv2d(\n",
    "            in_channels=input_channels, \n",
    "            out_channels=output_channel_for_each_path[2], \n",
    "            kernel_size=5, \n",
    "            padding=\"same\"\n",
    "            ) # 5*5卷积核，要same，上下左右各补两圈0\n",
    "        self.max_pooling = nn.MaxPool2d(\n",
    "            kernel_size=2, \n",
    "            stride=2, \n",
    "            )\n",
    "        \n",
    "    def forward(self, x):\n",
    "        conv1_1 = F.relu(self.conv1_1(x))\n",
    "        conv3_3 = F.relu(self.conv3_3(x))\n",
    "        conv5_5 = F.relu(self.conv5_5(x))\n",
    "        max_pooling = self.max_pooling(x) # 最大池化，图像尺寸缩小一半\n",
    "        \n",
    "        max_pooling_shape = max_pooling.shape[1:] #最后3个维度，即通道、高、宽\n",
    "        input_shape = x.shape[1:] # 输入的通道、高、宽\n",
    "        width_padding = (input_shape[-2] - max_pooling_shape[-2]) // 2 # // 输入尺寸，减去池化尺寸，再除以2\n",
    "        height_padding = (input_shape[-1] - max_pooling_shape[-1]) // 2\n",
    "        padded_pooling = F.pad(\n",
    "            max_pooling, \n",
    "            [width_padding, width_padding, height_padding, height_padding]\n",
    "            ) # [left, right, top, bottom]，在每个维度上填充的长度，默认填充最后两个维度，即高和宽，填充0\n",
    "        concat_output = torch.cat(\n",
    "            [conv1_1, conv3_3, conv5_5, padded_pooling], \n",
    "            dim=1\n",
    "            ) # 在通道维度上拼接\n",
    "        return concat_output\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T08:15:10.358796Z",
     "start_time": "2025-01-21T08:15:10.350873Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T08:17:50.092033Z",
     "iopub.status.busy": "2025-01-21T08:17:50.091777Z",
     "iopub.status.idle": "2025-01-21T08:17:50.105370Z",
     "shell.execute_reply": "2025-01-21T08:17:50.104922Z",
     "shell.execute_reply.started": "2025-01-21T08:17:50.092014Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "             model.0.weight             paramerters num: 864\n",
      "              model.0.bias              paramerters num: 32\n",
      "         model.3.conv1_1.weight         paramerters num: 512\n",
      "          model.3.conv1_1.bias          paramerters num: 16\n",
      "         model.3.conv3_3.weight         paramerters num: 4608\n",
      "          model.3.conv3_3.bias          paramerters num: 16\n",
      "         model.3.conv5_5.weight         paramerters num: 12800\n",
      "          model.3.conv5_5.bias          paramerters num: 16\n",
      "         model.4.conv1_1.weight         paramerters num: 1280\n",
      "          model.4.conv1_1.bias          paramerters num: 16\n",
      "         model.4.conv3_3.weight         paramerters num: 11520\n",
      "          model.4.conv3_3.bias          paramerters num: 16\n",
      "         model.4.conv5_5.weight         paramerters num: 32000\n",
      "          model.4.conv5_5.bias          paramerters num: 16\n",
      "         model.6.conv1_1.weight         paramerters num: 2048\n",
      "          model.6.conv1_1.bias          paramerters num: 16\n",
      "         model.6.conv3_3.weight         paramerters num: 18432\n",
      "          model.6.conv3_3.bias          paramerters num: 16\n",
      "         model.6.conv5_5.weight         paramerters num: 51200\n",
      "          model.6.conv5_5.bias          paramerters num: 16\n",
      "         model.7.conv1_1.weight         paramerters num: 2816\n",
      "          model.7.conv1_1.bias          paramerters num: 16\n",
      "         model.7.conv3_3.weight         paramerters num: 25344\n",
      "          model.7.conv3_3.bias          paramerters num: 16\n",
      "         model.7.conv5_5.weight         paramerters num: 70400\n",
      "          model.7.conv5_5.bias          paramerters num: 16\n",
      "            model.10.weight             paramerters num: 35840\n",
      "             model.10.bias              paramerters num: 10\n"
     ]
    }
   ],
   "source": [
    "class InceptionNet(nn.Module):\n",
    "    def __init__(self, num_classes=10):\n",
    "        super(InceptionNet, self).__init__()\n",
    "        self.model = nn.Sequential(\n",
    "            nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, stride=1, padding=\"same\"),\n",
    "            nn.ReLU(),\n",
    "            nn.MaxPool2d(kernel_size=2, stride=2),#16*16\n",
    "            InceptionBolck(input_channels=32, output_channel_for_each_path=[16, 16, 16]),#80*16*16\n",
    "            InceptionBolck(input_channels=80, output_channel_for_each_path=[16, 16, 16]),#128*16*16\n",
    "            nn.MaxPool2d(kernel_size=2, stride=2),#8*8\n",
    "            InceptionBolck(input_channels=128, output_channel_for_each_path=[16, 16, 16]),#176*8*8\n",
    "            InceptionBolck(input_channels=176, output_channel_for_each_path=[16, 16, 16]),#224*8*8\n",
    "            nn.MaxPool2d(kernel_size=2, stride=2),#224*4*4\n",
    "            nn.Flatten(),\n",
    "            nn.Linear(3584, num_classes)\n",
    "        )\n",
    "        self.init_weights()\n",
    "        \n",
    "    def init_weights(self):\n",
    "        \"\"\"使用 xavier 均匀分布来初始化全连接层、卷积层的权重 W\"\"\"\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, (nn.Linear, nn.Conv2d)):\n",
    "                nn.init.xavier_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "                \n",
    "    def forward(self, x):\n",
    "        return self.model(x)\n",
    "\n",
    "            \n",
    "for key, value in InceptionNet(len(class_names)).named_parameters():\n",
    "    print(f\"{key:^40}paramerters num: {np.prod(value.shape)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T08:15:01.892610Z",
     "start_time": "2025-01-21T08:15:01.887961Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T08:17:50.105989Z",
     "iopub.status.busy": "2025-01-21T08:17:50.105818Z",
     "iopub.status.idle": "2025-01-21T08:17:50.109871Z",
     "shell.execute_reply": "2025-01-21T08:17:50.109445Z",
     "shell.execute_reply.started": "2025-01-21T08:17:50.105971Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3584"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "224*4*4"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-25T02:18:21.466720300Z",
     "start_time": "2024-07-25T02:18:21.440027700Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-21T08:17:50.110593Z",
     "iopub.status.busy": "2025-01-21T08:17:50.110353Z",
     "iopub.status.idle": "2025-01-21T08:17:50.119409Z",
     "shell.execute_reply": "2025-01-21T08:17:50.118971Z",
     "shell.execute_reply.started": "2025-01-21T08:17:50.110575Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total params: 269,898\n"
     ]
    }
   ],
   "source": [
    "#计算总参数量\n",
    "total_params = sum(p.numel() for p in InceptionNet(len(class_names)).parameters() if p.requires_grad)\n",
    "print(f\"Total params: {total_params:,}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-26T01:31:06.814529600Z",
     "start_time": "2024-07-26T01:31:06.152598300Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-21T08:17:50.120038Z",
     "iopub.status.busy": "2025-01-21T08:17:50.119877Z",
     "iopub.status.idle": "2025-01-21T08:17:50.545575Z",
     "shell.execute_reply": "2025-01-21T08:17:50.545104Z",
     "shell.execute_reply.started": "2025-01-21T08:17:50.120020Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.10/site-packages/torchvision/models/inception.py:43: FutureWarning: The default weight initialization of inception_v3 will be changed in future releases of torchvision. If you wish to keep the old behavior (which leads to long initialization times due to scipy/scipy#11299), please set init_weights=True.\n",
      "  warnings.warn(\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "Inception3(\n",
       "  (Conv2d_1a_3x3): BasicConv2d(\n",
       "    (conv): Conv2d(3, 32, kernel_size=(3, 3), stride=(2, 2), bias=False)\n",
       "    (bn): BatchNorm2d(32, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "  )\n",
       "  (Conv2d_2a_3x3): BasicConv2d(\n",
       "    (conv): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), bias=False)\n",
       "    (bn): BatchNorm2d(32, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "  )\n",
       "  (Conv2d_2b_3x3): BasicConv2d(\n",
       "    (conv): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "    (bn): BatchNorm2d(64, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "  )\n",
       "  (maxpool1): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
       "  (Conv2d_3b_1x1): BasicConv2d(\n",
       "    (conv): Conv2d(64, 80, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "    (bn): BatchNorm2d(80, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "  )\n",
       "  (Conv2d_4a_3x3): BasicConv2d(\n",
       "    (conv): Conv2d(80, 192, kernel_size=(3, 3), stride=(1, 1), bias=False)\n",
       "    (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "  )\n",
       "  (maxpool2): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
       "  (Mixed_5b): InceptionA(\n",
       "    (branch1x1): BasicConv2d(\n",
       "      (conv): Conv2d(192, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(64, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch5x5_1): BasicConv2d(\n",
       "      (conv): Conv2d(192, 48, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(48, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch5x5_2): BasicConv2d(\n",
       "      (conv): Conv2d(48, 64, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2), bias=False)\n",
       "      (bn): BatchNorm2d(64, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3dbl_1): BasicConv2d(\n",
       "      (conv): Conv2d(192, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(64, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3dbl_2): BasicConv2d(\n",
       "      (conv): Conv2d(64, 96, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(96, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3dbl_3): BasicConv2d(\n",
       "      (conv): Conv2d(96, 96, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(96, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch_pool): BasicConv2d(\n",
       "      (conv): Conv2d(192, 32, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(32, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "  )\n",
       "  (Mixed_5c): InceptionA(\n",
       "    (branch1x1): BasicConv2d(\n",
       "      (conv): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(64, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch5x5_1): BasicConv2d(\n",
       "      (conv): Conv2d(256, 48, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(48, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch5x5_2): BasicConv2d(\n",
       "      (conv): Conv2d(48, 64, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2), bias=False)\n",
       "      (bn): BatchNorm2d(64, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3dbl_1): BasicConv2d(\n",
       "      (conv): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(64, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3dbl_2): BasicConv2d(\n",
       "      (conv): Conv2d(64, 96, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(96, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3dbl_3): BasicConv2d(\n",
       "      (conv): Conv2d(96, 96, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(96, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch_pool): BasicConv2d(\n",
       "      (conv): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(64, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "  )\n",
       "  (Mixed_5d): InceptionA(\n",
       "    (branch1x1): BasicConv2d(\n",
       "      (conv): Conv2d(288, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(64, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch5x5_1): BasicConv2d(\n",
       "      (conv): Conv2d(288, 48, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(48, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch5x5_2): BasicConv2d(\n",
       "      (conv): Conv2d(48, 64, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2), bias=False)\n",
       "      (bn): BatchNorm2d(64, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3dbl_1): BasicConv2d(\n",
       "      (conv): Conv2d(288, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(64, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3dbl_2): BasicConv2d(\n",
       "      (conv): Conv2d(64, 96, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(96, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3dbl_3): BasicConv2d(\n",
       "      (conv): Conv2d(96, 96, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(96, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch_pool): BasicConv2d(\n",
       "      (conv): Conv2d(288, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(64, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "  )\n",
       "  (Mixed_6a): InceptionB(\n",
       "    (branch3x3): BasicConv2d(\n",
       "      (conv): Conv2d(288, 384, kernel_size=(3, 3), stride=(2, 2), bias=False)\n",
       "      (bn): BatchNorm2d(384, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3dbl_1): BasicConv2d(\n",
       "      (conv): Conv2d(288, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(64, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3dbl_2): BasicConv2d(\n",
       "      (conv): Conv2d(64, 96, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(96, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3dbl_3): BasicConv2d(\n",
       "      (conv): Conv2d(96, 96, kernel_size=(3, 3), stride=(2, 2), bias=False)\n",
       "      (bn): BatchNorm2d(96, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "  )\n",
       "  (Mixed_6b): InceptionC(\n",
       "    (branch1x1): BasicConv2d(\n",
       "      (conv): Conv2d(768, 192, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7_1): BasicConv2d(\n",
       "      (conv): Conv2d(768, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(128, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7_2): BasicConv2d(\n",
       "      (conv): Conv2d(128, 128, kernel_size=(1, 7), stride=(1, 1), padding=(0, 3), bias=False)\n",
       "      (bn): BatchNorm2d(128, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7_3): BasicConv2d(\n",
       "      (conv): Conv2d(128, 192, kernel_size=(7, 1), stride=(1, 1), padding=(3, 0), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7dbl_1): BasicConv2d(\n",
       "      (conv): Conv2d(768, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(128, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7dbl_2): BasicConv2d(\n",
       "      (conv): Conv2d(128, 128, kernel_size=(7, 1), stride=(1, 1), padding=(3, 0), bias=False)\n",
       "      (bn): BatchNorm2d(128, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7dbl_3): BasicConv2d(\n",
       "      (conv): Conv2d(128, 128, kernel_size=(1, 7), stride=(1, 1), padding=(0, 3), bias=False)\n",
       "      (bn): BatchNorm2d(128, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7dbl_4): BasicConv2d(\n",
       "      (conv): Conv2d(128, 128, kernel_size=(7, 1), stride=(1, 1), padding=(3, 0), bias=False)\n",
       "      (bn): BatchNorm2d(128, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7dbl_5): BasicConv2d(\n",
       "      (conv): Conv2d(128, 192, kernel_size=(1, 7), stride=(1, 1), padding=(0, 3), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch_pool): BasicConv2d(\n",
       "      (conv): Conv2d(768, 192, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "  )\n",
       "  (Mixed_6c): InceptionC(\n",
       "    (branch1x1): BasicConv2d(\n",
       "      (conv): Conv2d(768, 192, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7_1): BasicConv2d(\n",
       "      (conv): Conv2d(768, 160, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(160, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7_2): BasicConv2d(\n",
       "      (conv): Conv2d(160, 160, kernel_size=(1, 7), stride=(1, 1), padding=(0, 3), bias=False)\n",
       "      (bn): BatchNorm2d(160, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7_3): BasicConv2d(\n",
       "      (conv): Conv2d(160, 192, kernel_size=(7, 1), stride=(1, 1), padding=(3, 0), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7dbl_1): BasicConv2d(\n",
       "      (conv): Conv2d(768, 160, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(160, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7dbl_2): BasicConv2d(\n",
       "      (conv): Conv2d(160, 160, kernel_size=(7, 1), stride=(1, 1), padding=(3, 0), bias=False)\n",
       "      (bn): BatchNorm2d(160, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7dbl_3): BasicConv2d(\n",
       "      (conv): Conv2d(160, 160, kernel_size=(1, 7), stride=(1, 1), padding=(0, 3), bias=False)\n",
       "      (bn): BatchNorm2d(160, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7dbl_4): BasicConv2d(\n",
       "      (conv): Conv2d(160, 160, kernel_size=(7, 1), stride=(1, 1), padding=(3, 0), bias=False)\n",
       "      (bn): BatchNorm2d(160, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7dbl_5): BasicConv2d(\n",
       "      (conv): Conv2d(160, 192, kernel_size=(1, 7), stride=(1, 1), padding=(0, 3), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch_pool): BasicConv2d(\n",
       "      (conv): Conv2d(768, 192, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "  )\n",
       "  (Mixed_6d): InceptionC(\n",
       "    (branch1x1): BasicConv2d(\n",
       "      (conv): Conv2d(768, 192, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7_1): BasicConv2d(\n",
       "      (conv): Conv2d(768, 160, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(160, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7_2): BasicConv2d(\n",
       "      (conv): Conv2d(160, 160, kernel_size=(1, 7), stride=(1, 1), padding=(0, 3), bias=False)\n",
       "      (bn): BatchNorm2d(160, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7_3): BasicConv2d(\n",
       "      (conv): Conv2d(160, 192, kernel_size=(7, 1), stride=(1, 1), padding=(3, 0), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7dbl_1): BasicConv2d(\n",
       "      (conv): Conv2d(768, 160, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(160, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7dbl_2): BasicConv2d(\n",
       "      (conv): Conv2d(160, 160, kernel_size=(7, 1), stride=(1, 1), padding=(3, 0), bias=False)\n",
       "      (bn): BatchNorm2d(160, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7dbl_3): BasicConv2d(\n",
       "      (conv): Conv2d(160, 160, kernel_size=(1, 7), stride=(1, 1), padding=(0, 3), bias=False)\n",
       "      (bn): BatchNorm2d(160, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7dbl_4): BasicConv2d(\n",
       "      (conv): Conv2d(160, 160, kernel_size=(7, 1), stride=(1, 1), padding=(3, 0), bias=False)\n",
       "      (bn): BatchNorm2d(160, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7dbl_5): BasicConv2d(\n",
       "      (conv): Conv2d(160, 192, kernel_size=(1, 7), stride=(1, 1), padding=(0, 3), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch_pool): BasicConv2d(\n",
       "      (conv): Conv2d(768, 192, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "  )\n",
       "  (Mixed_6e): InceptionC(\n",
       "    (branch1x1): BasicConv2d(\n",
       "      (conv): Conv2d(768, 192, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7_1): BasicConv2d(\n",
       "      (conv): Conv2d(768, 192, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7_2): BasicConv2d(\n",
       "      (conv): Conv2d(192, 192, kernel_size=(1, 7), stride=(1, 1), padding=(0, 3), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7_3): BasicConv2d(\n",
       "      (conv): Conv2d(192, 192, kernel_size=(7, 1), stride=(1, 1), padding=(3, 0), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7dbl_1): BasicConv2d(\n",
       "      (conv): Conv2d(768, 192, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7dbl_2): BasicConv2d(\n",
       "      (conv): Conv2d(192, 192, kernel_size=(7, 1), stride=(1, 1), padding=(3, 0), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7dbl_3): BasicConv2d(\n",
       "      (conv): Conv2d(192, 192, kernel_size=(1, 7), stride=(1, 1), padding=(0, 3), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7dbl_4): BasicConv2d(\n",
       "      (conv): Conv2d(192, 192, kernel_size=(7, 1), stride=(1, 1), padding=(3, 0), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7dbl_5): BasicConv2d(\n",
       "      (conv): Conv2d(192, 192, kernel_size=(1, 7), stride=(1, 1), padding=(0, 3), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch_pool): BasicConv2d(\n",
       "      (conv): Conv2d(768, 192, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "  )\n",
       "  (AuxLogits): InceptionAux(\n",
       "    (conv0): BasicConv2d(\n",
       "      (conv): Conv2d(768, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(128, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (conv1): BasicConv2d(\n",
       "      (conv): Conv2d(128, 768, kernel_size=(5, 5), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(768, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (fc): Linear(in_features=768, out_features=1000, bias=True)\n",
       "  )\n",
       "  (Mixed_7a): InceptionD(\n",
       "    (branch3x3_1): BasicConv2d(\n",
       "      (conv): Conv2d(768, 192, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3_2): BasicConv2d(\n",
       "      (conv): Conv2d(192, 320, kernel_size=(3, 3), stride=(2, 2), bias=False)\n",
       "      (bn): BatchNorm2d(320, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7x3_1): BasicConv2d(\n",
       "      (conv): Conv2d(768, 192, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7x3_2): BasicConv2d(\n",
       "      (conv): Conv2d(192, 192, kernel_size=(1, 7), stride=(1, 1), padding=(0, 3), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7x3_3): BasicConv2d(\n",
       "      (conv): Conv2d(192, 192, kernel_size=(7, 1), stride=(1, 1), padding=(3, 0), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch7x7x3_4): BasicConv2d(\n",
       "      (conv): Conv2d(192, 192, kernel_size=(3, 3), stride=(2, 2), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "  )\n",
       "  (Mixed_7b): InceptionE(\n",
       "    (branch1x1): BasicConv2d(\n",
       "      (conv): Conv2d(1280, 320, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(320, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3_1): BasicConv2d(\n",
       "      (conv): Conv2d(1280, 384, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(384, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3_2a): BasicConv2d(\n",
       "      (conv): Conv2d(384, 384, kernel_size=(1, 3), stride=(1, 1), padding=(0, 1), bias=False)\n",
       "      (bn): BatchNorm2d(384, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3_2b): BasicConv2d(\n",
       "      (conv): Conv2d(384, 384, kernel_size=(3, 1), stride=(1, 1), padding=(1, 0), bias=False)\n",
       "      (bn): BatchNorm2d(384, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3dbl_1): BasicConv2d(\n",
       "      (conv): Conv2d(1280, 448, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(448, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3dbl_2): BasicConv2d(\n",
       "      (conv): Conv2d(448, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(384, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3dbl_3a): BasicConv2d(\n",
       "      (conv): Conv2d(384, 384, kernel_size=(1, 3), stride=(1, 1), padding=(0, 1), bias=False)\n",
       "      (bn): BatchNorm2d(384, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3dbl_3b): BasicConv2d(\n",
       "      (conv): Conv2d(384, 384, kernel_size=(3, 1), stride=(1, 1), padding=(1, 0), bias=False)\n",
       "      (bn): BatchNorm2d(384, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch_pool): BasicConv2d(\n",
       "      (conv): Conv2d(1280, 192, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "  )\n",
       "  (Mixed_7c): InceptionE(\n",
       "    (branch1x1): BasicConv2d(\n",
       "      (conv): Conv2d(2048, 320, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(320, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3_1): BasicConv2d(\n",
       "      (conv): Conv2d(2048, 384, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(384, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3_2a): BasicConv2d(\n",
       "      (conv): Conv2d(384, 384, kernel_size=(1, 3), stride=(1, 1), padding=(0, 1), bias=False)\n",
       "      (bn): BatchNorm2d(384, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3_2b): BasicConv2d(\n",
       "      (conv): Conv2d(384, 384, kernel_size=(3, 1), stride=(1, 1), padding=(1, 0), bias=False)\n",
       "      (bn): BatchNorm2d(384, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3dbl_1): BasicConv2d(\n",
       "      (conv): Conv2d(2048, 448, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(448, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3dbl_2): BasicConv2d(\n",
       "      (conv): Conv2d(448, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(384, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3dbl_3a): BasicConv2d(\n",
       "      (conv): Conv2d(384, 384, kernel_size=(1, 3), stride=(1, 1), padding=(0, 1), bias=False)\n",
       "      (bn): BatchNorm2d(384, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch3x3dbl_3b): BasicConv2d(\n",
       "      (conv): Conv2d(384, 384, kernel_size=(3, 1), stride=(1, 1), padding=(1, 0), bias=False)\n",
       "      (bn): BatchNorm2d(384, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (branch_pool): BasicConv2d(\n",
       "      (conv): Conv2d(2048, 192, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "      (bn): BatchNorm2d(192, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "  )\n",
       "  (avgpool): AdaptiveAvgPool2d(output_size=(1, 1))\n",
       "  (dropout): Dropout(p=0.5, inplace=False)\n",
       "  (fc): Linear(in_features=2048, out_features=1000, bias=True)\n",
       ")"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from torchvision.models import inception_v3\n",
    "model=inception_v3()\n",
    "model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-26T01:33:28.411511600Z",
     "start_time": "2024-07-26T01:33:28.399487300Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-21T08:17:50.546407Z",
     "iopub.status.busy": "2025-01-21T08:17:50.546152Z",
     "iopub.status.idle": "2025-01-21T08:17:50.550387Z",
     "shell.execute_reply": "2025-01-21T08:17:50.549859Z",
     "shell.execute_reply.started": "2025-01-21T08:17:50.546387Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total params: 27,161,264\n"
     ]
    }
   ],
   "source": [
    "total_params = sum(p.numel() for p in model.parameters() if p.requires_grad)\n",
    "print(f\"Total params: {total_params:,}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-26T03:10:23.045400900Z",
     "start_time": "2024-07-26T03:10:21.357935400Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-21T08:17:50.551205Z",
     "iopub.status.busy": "2025-01-21T08:17:50.551023Z",
     "iopub.status.idle": "2025-01-21T08:18:24.369013Z",
     "shell.execute_reply": "2025-01-21T08:18:24.368508Z",
     "shell.execute_reply.started": "2025-01-21T08:17:50.551187Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.10/site-packages/torchvision/models/_utils.py:208: UserWarning: The parameter 'pretrained' is deprecated since 0.13 and may be removed in the future, please use 'weights' instead.\n",
      "  warnings.warn(\n",
      "/usr/local/lib/python3.10/site-packages/torchvision/models/_utils.py:223: UserWarning: Arguments other than a weight enum or `None` for 'weights' are deprecated since 0.13 and may be removed in the future. The current behavior is equivalent to passing `weights=VGG19_Weights.IMAGENET1K_V1`. You can also use `weights=VGG19_Weights.DEFAULT` to get the most up-to-date weights.\n",
      "  warnings.warn(msg)\n",
      "Downloading: \"https://download.pytorch.org/models/vgg19-dcbb9e9d.pth\" to /root/.cache/torch/hub/checkpoints/vgg19-dcbb9e9d.pth\n",
      "100%|██████████| 548M/548M [00:31<00:00, 18.3MB/s] \n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "VGG(\n",
       "  (features): Sequential(\n",
       "    (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
       "    (1): ReLU(inplace=True)\n",
       "    (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
       "    (3): ReLU(inplace=True)\n",
       "    (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
       "    (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
       "    (6): ReLU(inplace=True)\n",
       "    (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
       "    (8): ReLU(inplace=True)\n",
       "    (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
       "    (10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
       "    (11): ReLU(inplace=True)\n",
       "    (12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
       "    (13): ReLU(inplace=True)\n",
       "    (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
       "    (15): ReLU(inplace=True)\n",
       "    (16): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
       "    (17): ReLU(inplace=True)\n",
       "    (18): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
       "    (19): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
       "    (20): ReLU(inplace=True)\n",
       "    (21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
       "    (22): ReLU(inplace=True)\n",
       "    (23): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
       "    (24): ReLU(inplace=True)\n",
       "    (25): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
       "    (26): ReLU(inplace=True)\n",
       "    (27): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
       "    (28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
       "    (29): ReLU(inplace=True)\n",
       "    (30): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
       "    (31): ReLU(inplace=True)\n",
       "    (32): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
       "    (33): ReLU(inplace=True)\n",
       "    (34): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
       "    (35): ReLU(inplace=True)\n",
       "    (36): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
       "  )\n",
       "  (avgpool): AdaptiveAvgPool2d(output_size=(7, 7))\n",
       "  (classifier): Sequential(\n",
       "    (0): Linear(in_features=25088, out_features=4096, bias=True)\n",
       "    (1): ReLU(inplace=True)\n",
       "    (2): Dropout(p=0.5, inplace=False)\n",
       "    (3): Linear(in_features=4096, out_features=4096, bias=True)\n",
       "    (4): ReLU(inplace=True)\n",
       "    (5): Dropout(p=0.5, inplace=False)\n",
       "    (6): Linear(in_features=4096, out_features=1000, bias=True)\n",
       "  )\n",
       ")"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from torchvision.models import vgg19\n",
    "model=vgg19(pretrained=True)\n",
    "model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-04-29T07:32:25.732902700Z",
     "start_time": "2024-04-29T07:32:25.719926400Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-21T08:18:24.369868Z",
     "iopub.status.busy": "2025-01-21T08:18:24.369631Z",
     "iopub.status.idle": "2025-01-21T08:18:24.373573Z",
     "shell.execute_reply": "2025-01-21T08:18:24.373010Z",
     "shell.execute_reply.started": "2025-01-21T08:18:24.369848Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3584"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "224*4*4"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-24T03:36:24.264691400Z",
     "start_time": "2024-07-24T03:36:24.252349Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-21T08:18:24.374287Z",
     "iopub.status.busy": "2025-01-21T08:18:24.374093Z",
     "iopub.status.idle": "2025-01-21T08:18:24.380675Z",
     "shell.execute_reply": "2025-01-21T08:18:24.380214Z",
     "shell.execute_reply.started": "2025-01-21T08:18:24.374268Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[[[ 0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,\n",
       "            0.0000],\n",
       "          [ 0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,\n",
       "            0.0000],\n",
       "          [ 0.0000,  0.0000,  0.0910, -0.4735, -2.0639, -0.8593,  0.0000,\n",
       "            0.0000],\n",
       "          [ 0.0000,  0.0000, -1.8001, -0.3824,  0.3311,  0.5896,  0.0000,\n",
       "            0.0000],\n",
       "          [ 0.0000,  0.0000,  0.8563, -0.3901, -2.2399, -0.6642,  0.0000,\n",
       "            0.0000],\n",
       "          [ 0.0000,  0.0000, -0.7637, -0.6928, -0.2442, -0.4200,  0.0000,\n",
       "            0.0000],\n",
       "          [ 0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,\n",
       "            0.0000],\n",
       "          [ 0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,\n",
       "            0.0000]]]])"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#给F.pad写一个例子\n",
    "input_shape = (3, 8, 8)\n",
    "max_pooling_shape = (4, 4)\n",
    "width_padding = (8 - 4) // 2\n",
    "height_padding = (8 - 4) // 2\n",
    "padded_pooling = F.pad(\n",
    "    torch.randn(1, 1,4,4),\n",
    "    [width_padding, width_padding, height_padding, height_padding]\n",
    "    ) # [left, right, top, bottom]，在每个维度上填充的长度，默认填充最后两个维度，即高和宽，填充0\n",
    "padded_pooling"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义损失函数和优化器"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-21T08:18:24.381292Z",
     "iopub.status.busy": "2025-01-21T08:18:24.381135Z",
     "iopub.status.idle": "2025-01-21T08:18:24.383887Z",
     "shell.execute_reply": "2025-01-21T08:18:24.383326Z",
     "shell.execute_reply.started": "2025-01-21T08:18:24.381275Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [],
   "source": [
    "import torch.optim as optim\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 训练\n",
    "\n",
    "pytorch的训练需要自行实现，包括\n",
    "1. 定义损失函数\n",
    "2. 定义优化器\n",
    "3. 定义训练步\n",
    "4. 训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T08:18:24.384487Z",
     "iopub.status.busy": "2025-01-21T08:18:24.384336Z",
     "iopub.status.idle": "2025-01-21T08:18:24.411153Z",
     "shell.execute_reply": "2025-01-21T08:18:24.410694Z",
     "shell.execute_reply.started": "2025-01-21T08:18:24.384470Z"
    }
   },
   "outputs": [],
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "        preds = logits.argmax(axis=-1)    # 验证集预测\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "        \n",
    "    acc = accuracy_score(label_list, pred_list)\n",
    "    return np.mean(loss_list), acc\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### TensorBoard 可视化\n",
    "\n",
    "\n",
    "训练过程中可以使用如下命令启动tensorboard服务。\n",
    "\n",
    "```shell\n",
    "tensorboard \\\n",
    "    --logdir=runs \\     # log 存放路径\n",
    "    --host 0.0.0.0 \\    # ip\n",
    "    --port 8848         # 端口\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T08:18:24.411961Z",
     "iopub.status.busy": "2025-01-21T08:18:24.411694Z",
     "iopub.status.idle": "2025-01-21T08:18:24.471493Z",
     "shell.execute_reply": "2025-01-21T08:18:24.470962Z",
     "shell.execute_reply.started": "2025-01-21T08:18:24.411942Z"
    }
   },
   "outputs": [],
   "source": [
    "from torch.utils.tensorboard import SummaryWriter\n",
    "\n",
    "\n",
    "class TensorBoardCallback:\n",
    "    def __init__(self, log_dir, flush_secs=10):\n",
    "        \"\"\"\n",
    "        Args:\n",
    "            log_dir (str): dir to write log.\n",
    "            flush_secs (int, optional): write to dsk each flush_secs seconds. Defaults to 10.\n",
    "        \"\"\"\n",
    "        self.writer = SummaryWriter(log_dir=log_dir, flush_secs=flush_secs)\n",
    "\n",
    "    def draw_model(self, model, input_shape):\n",
    "        self.writer.add_graph(model, input_to_model=torch.randn(input_shape))\n",
    "        \n",
    "    def add_loss_scalars(self, step, loss, val_loss):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/loss\", \n",
    "            tag_scalar_dict={\"loss\": loss, \"val_loss\": val_loss},\n",
    "            global_step=step,\n",
    "            )\n",
    "        \n",
    "    def add_acc_scalars(self, step, acc, val_acc):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/accuracy\",\n",
    "            tag_scalar_dict={\"accuracy\": acc, \"val_accuracy\": val_acc},\n",
    "            global_step=step,\n",
    "        )\n",
    "        \n",
    "    def add_lr_scalars(self, step, learning_rate):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/learning_rate\",\n",
    "            tag_scalar_dict={\"learning_rate\": learning_rate},\n",
    "            global_step=step,\n",
    "            \n",
    "        )\n",
    "    \n",
    "    def __call__(self, step, **kwargs):\n",
    "        # add loss\n",
    "        loss = kwargs.pop(\"loss\", None)\n",
    "        val_loss = kwargs.pop(\"val_loss\", None)\n",
    "        if loss is not None and val_loss is not None:\n",
    "            self.add_loss_scalars(step, loss, val_loss)\n",
    "        # add acc\n",
    "        acc = kwargs.pop(\"acc\", None)\n",
    "        val_acc = kwargs.pop(\"val_acc\", None)\n",
    "        if acc is not None and val_acc is not None:\n",
    "            self.add_acc_scalars(step, acc, val_acc)\n",
    "        # add lr\n",
    "        learning_rate = kwargs.pop(\"lr\", None)\n",
    "        if learning_rate is not None:\n",
    "            self.add_lr_scalars(step, learning_rate)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Save Best\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T08:18:24.472466Z",
     "iopub.status.busy": "2025-01-21T08:18:24.472117Z",
     "iopub.status.idle": "2025-01-21T08:18:24.477717Z",
     "shell.execute_reply": "2025-01-21T08:18:24.477248Z",
     "shell.execute_reply.started": "2025-01-21T08:18:24.472444Z"
    }
   },
   "outputs": [],
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=5000, save_best_only=True):\n",
    "        \"\"\"\n",
    "        Save checkpoints each save_epoch epoch. \n",
    "        We save checkpoint by epoch in this implementation.\n",
    "        Usually, training scripts with pytorch evaluating model and save checkpoint by step.\n",
    "\n",
    "        Args:\n",
    "            save_dir (str): dir to save checkpoint\n",
    "            save_epoch (int, optional): the frequency to save checkpoint. Defaults to 1.\n",
    "            save_best_only (bool, optional): If True, only save the best model or save each model at every epoch.\n",
    "        \"\"\"\n",
    "        self.save_dir = save_dir\n",
    "        self.save_step = save_step\n",
    "        self.save_best_only = save_best_only\n",
    "        self.best_metrics = -1\n",
    "        \n",
    "        # mkdir\n",
    "        if not os.path.exists(self.save_dir):\n",
    "            os.mkdir(self.save_dir)\n",
    "        \n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return\n",
    "        \n",
    "        if self.save_best_only:\n",
    "            assert metric is not None\n",
    "            if metric >= self.best_metrics:\n",
    "                # save checkpoints\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"best.ckpt\"))\n",
    "                # update best metrics\n",
    "                self.best_metrics = metric\n",
    "        else:\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Early Stop"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T08:18:24.478600Z",
     "iopub.status.busy": "2025-01-21T08:18:24.478242Z",
     "iopub.status.idle": "2025-01-21T08:18:24.482533Z",
     "shell.execute_reply": "2025-01-21T08:18:24.482085Z",
     "shell.execute_reply.started": "2025-01-21T08:18:24.478580Z"
    }
   },
   "outputs": [],
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T08:18:24.483343Z",
     "iopub.status.busy": "2025-01-21T08:18:24.483173Z",
     "iopub.status.idle": "2025-01-21T08:19:59.858049Z",
     "shell.execute_reply": "2025-01-21T08:19:59.857482Z",
     "shell.execute_reply.started": "2025-01-21T08:18:24.483325Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 60%|██████    | 8448/14080 [01:35<01:03, 88.85it/s, epoch=11] "
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Early stop at epoch 12 / global_step 8448\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    "                preds = logits.argmax(axis=-1)\n",
    "            \n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())    \n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"acc\": acc, \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss, val_acc = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"acc\": val_acc, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "                    \n",
    "                    # 1. 使用 tensorboard 可视化\n",
    "                    if tensorboard_callback is not None:\n",
    "                        tensorboard_callback(\n",
    "                            global_step, \n",
    "                            loss=loss, val_loss=val_loss,\n",
    "                            acc=acc, val_acc=val_acc,\n",
    "                            lr=optimizer.param_groups[0][\"lr\"],\n",
    "                            )\n",
    "                \n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), metric=val_acc)\n",
    "\n",
    "                    # 3. 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(val_acc)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 20\n",
    "\n",
    "model = InceptionNet(num_classes=10)\n",
    "\n",
    "# 1. 定义损失函数 采用交叉熵损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "# 2. 定义优化器 采用 Rmsprop\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.RMSprop(model.parameters(), lr=0.001, alpha=0.9, eps=1e-07)\n",
    "\n",
    "# 1. tensorboard 可视化\n",
    "if not os.path.exists(\"runs\"):\n",
    "    os.mkdir(\"runs\")\n",
    "    \n",
    "exp_name = \"inception_net\"\n",
    "tensorboard_callback = TensorBoardCallback(f\"runs/{exp_name}\")\n",
    "tensorboard_callback.draw_model(model, [1, 3, IMAGE_SIZE, IMAGE_SIZE])\n",
    "# 2. save best\n",
    "if not os.path.exists(\"checkpoints\"):\n",
    "    os.makedirs(\"checkpoints\")\n",
    "save_ckpt_callback = SaveCheckpointsCallback(f\"checkpoints/{exp_name}\", save_step=len(train_dl), save_best_only=True)\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=5)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model, \n",
    "    train_dl, \n",
    "    eval_dl, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=tensorboard_callback,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_dl)\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T08:19:59.861123Z",
     "iopub.status.busy": "2025-01-21T08:19:59.860845Z",
     "iopub.status.idle": "2025-01-21T08:20:00.078331Z",
     "shell.execute_reply": "2025-01-21T08:20:00.077666Z",
     "shell.execute_reply.started": "2025-01-21T08:19:59.861099Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAy0AAAHACAYAAACvVYdzAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAA845JREFUeJzsnXeYXGXZ/z/Td2dLdpPdTe8FEkgCBIgB6SkQiKKICkiJAgrEF4g1PxSIvhIVBZSqvCDFAohgI0JCMNQ0AoEASUjvyfY2s9PP748zz5kzdWe2l/tzXXvtzswpzzw7Z/f5nvv+3rdF0zQNQRAEQRAEQRCEHoq1uwcgCIIgCIIgCIKQCREtgiAIgiAIgiD0aES0CIIgCIIgCILQoxHRIgiCIAiCIAhCj0ZEiyAIgiAIgiAIPRoRLYIgCIIgCIIg9GhEtAiCIAiCIAiC0KMR0SIIgiAIgiAIQo/G3tUnjEQiHDp0iKKiIiwWS1efXhAEod+iaRpNTU0MGzYMq1XuWSnk/5IgCEL3ke3/pi4XLYcOHWLkyJFdfVpBEAQhyv79+xkxYkR3D6PHIP+XBEEQup/W/jd1uWgpKioC9IEVFxfnvH8wGGTFihXMnTsXh8PR0cPrU8hc5YbMV27IfOVGT5ivxsZGRo4cafwdFnTk/1LXIvOVGzJfuSHzlRs9Yb6y/d/U5aJFhd6Li4vb/M/B7XZTXFwsH8ZWkLnKDZmv3JD5yo2eNF+SAhWP/F/qWmS+ckPmKzdkvnKjJ81Xa/+bJKlZEARBEARBEIQejYgWQRAEQRAEQRB6NCJaBEEQBEEQBEHo0XS5p0UQhJ6JpmmEQiHC4XBW2weDQex2Oz6fL+t9+jNdMV82mw273S6elU4g0/Uh10JudPV8yXUhCH0DES2CIBAIBDh8+DBerzfrfTRNY8iQIezfv18WA1nQVfPldrsZOnQoTqez087R32jt+pBrITe6Y77kuhCE3o+IFkHo50QiEXbv3o3NZmPYsGE4nc6sFhKRSITm5mYKCwulUWEWdPZ8aZpGIBCgqqqK3bt3M3HiRPm9dADZXB9yLeRGV86XXBeC0HcQ0SII/ZxAIEAkEmHkyJG43e6s94tEIgQCAfLy8mQRkAVdMV/5+fk4HA727t1rnEtoH9lcH3It5EZXz5dcF4LQN5C/roIgAMhiq48gv8fOQea1dyO/P0Ho/chVLAiCIPQoHnzwQcaMGUNeXh4zZ85k/fr1Gbe/7777OOaYY8jPz2fkyJHceuut+Hy+LhqtIAiC0BWIaBEEQRB6DM8++yyLFy/mjjvu4L333mP69OnMmzePysrKlNv/+c9/5oc//CF33HEHW7Zs4bHHHuPZZ5/l//2//9fFIxcEQRA6ExEtgiAIwJgxY7jvvvs65FirV6/GYrFQX1/fIcfrT9xzzz1cd911LFy4kClTpvDII4/gdrt5/PHHU27/zjvvcPrpp3P55ZczZswY5s6dy2WXXdZqdEbIjY68PgRBENqCiBZBEHotZ599NrfcckuHHGvDhg1cf/31HXIsoW0EAgE2btzI7NmzjeesViuzZ89mzZo1Kfc57bTT2LhxoyFSdu3axfLly5k/f36XjLknI9eHIAh9CakeJghCn0XTNMLhMHZ763/qysvLu2BEQiaqq6sJh8MMHjw47vnBgwezdevWlPtcfvnlVFdX89nPftZoAPmtb30rY3qY3+/H7/cbjxsbGwG96WEwGIzbNhgMomkakUiESCSS8niaphnf023TXWQaUy7Xx6BBgwA65P11x3xFIhE0TSMYDGKz2brknB2F+kwmfjaF1Mh85UZPmK9sz927RMumP2N/536mMBaQu2iC0BlomkZLsPUu1ZFIhJZAGHsg1GGVefIdtqybzV1zzTW8/vrrvP766/zmN78B4A9/+AMLFy5k+fLl/OhHP2Lz5s2sWLGCkSNHsnjxYtauXYvH42Hy5MksW7Ys7o7+mDFjuOWWW4w70xaLhUcffZSXXnqJV155heHDh/PrX/+az33uc216b3/729+4/fbb2bVrF0OHDuXb3/423/nOd4zXH3roIe69917279/PgAEDOOOMM3j++ecBeP7551m6dCk7duzA7XZz4okn8o9//IOCgoI2jaUvsXr1au666y4eeughZs6cyY4dO7j55pv56U9/yo9//OOU+yxbtoylS5cmPb9ixYqkssZ2u50hQ4bQ3NxMIBAA9GvEF0xebLfU1Lf/DbVCnsOa1TVy4403GtfHb3/7W0AvcHDTTTfx3HPP8bOf/YxPPvmEF154geHDh3Pbbbfx7rvv4vV6mTRpErfffjtnn322cbxp06Zxww03cMMNNwBQWlrKb37zG1asWMFrr73G0KFD+elPf5pVhCscDnPLLbfwxhtvUFlZyYgR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6Pv3v/89w4YNS2pU9/nPf56vf/3r7Ny5k89//vMMHjyYwsJCTjnlFF599dUOm6bNmzdz7rnnkp+fz6BBg7j++utpbm42Xl+9ejWnnnoqBQUFlJSUcMYZZ7Bv3z4APvjgA8455xyKioooLi5mxowZvPvuux02NqGTSXWNdPS10M5rpKuvj3vuuYepU6dSUFDAyJEjufHGG+OuB4C3336bs88+m8LCQsaMGcP5559PXV0doDec/OUvf8mECRNwuVyMGjWKn/3sZ20ej9B7aJHqYRkxC43GluznJxzR8AXjr39zpCYxatMbjPg5lTzuKUh6mCB0IkEv3DWs1c2sQElHn/v/HQJnQVabXnrppXz729/mv//9L+eddx4AtbW1vPzyyyxfvpzm5mbmz5/Pz372M1wuF0899RQLFixg27ZtjBo1ql3D9Hg8zJs3j1mzZrFhwwYqKyu59tprWbRoEU888QShUIiLL76Y6667jr/85S8EAgHWrl2LxWIB4IorruDEE0/k4YcfxmazsWnTJulW35tIuEY65VpIR5bXSFdfH1arld/+9reMHTuWXbt2ceONN/L973+fhx56CIBNmzZx3nnn8fWvf517770Xn8/Hu+++SzisL5SWLFnCo48+yr333stnP/tZDh8+zNatW3Meh9D7iEsPE09LEnFm+hwiLeYITVmhk+rmQJxnSP08qMBJjSfQK6JcvVO0GJEWES2C0F8pLS3lggsu4M9//rOxKHv++ecpKyvjnHPOwWq1Mn36dGP7n/70p7z44ov885//ZNGiRe0695///Gd8Ph9PPfUUBQX6AvKBBx5gwYIF/OIXv8DhcNDQ0MBFF13E+PHjATjmmGNobGwEYN++fXzve9/j2GOPBWDixIntGo8gJNLV18ctt9xi/DxmzBj+93//l29961uGaPnlL3/JySefzEMPPUQkEqGxsZGZM2ditVppamriN7/5DQ888ABXX301AOPHj+ezn/1sO2ag77JhTy3jygoYVOjq7qF0CHElj3vBwrmriYu05CDqlBfGbrUwsCAqWvzJqXgVRS5qPIGUnpajjT4O1rdw0qjStg6/Q+mdosXwtEh6mCB0OA63fje3FSKRCI1NTRQXFWG1dlCmqcOd0+ZXXHEF1113HQ899BAul4s//elPfPWrX8VqtdLc3Mydd97JSy+9xOHDhwmFQrS0tBgp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",
      "text/plain": [
       "<Figure size 1000x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    fig_num = len(train_df.columns)\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(5 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):    \n",
    "        axs[idx].plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        axs[idx].grid()\n",
    "        axs[idx].legend()\n",
    "        # axs[idx].set_xticks(range(0, train_df.index[-1], 5000))\n",
    "        # axs[idx].set_xticklabels(map(lambda x: f\"{int(x/1000)}k\", range(0, train_df.index[-1], 5000)))\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "    \n",
    "    plt.show()\n",
    "\n",
    "plot_learning_curves(record, sample_step=100)  #横坐标是 steps"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 评估"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T08:20:00.079168Z",
     "iopub.status.busy": "2025-01-21T08:20:00.078970Z",
     "iopub.status.idle": "2025-01-21T08:20:00.806024Z",
     "shell.execute_reply": "2025-01-21T08:20:00.805429Z",
     "shell.execute_reply.started": "2025-01-21T08:20:00.079146Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/tmp/ipykernel_4391/3500857114.py:4: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.\n",
      "  model.load_state_dict(torch.load(f\"checkpoints/{exp_name}/best.ckpt\", map_location=\"cpu\"))\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.9089\n",
      "accuracy: 0.6872\n"
     ]
    }
   ],
   "source": [
    "# dataload for evaluating\n",
    "\n",
    "# load checkpoints\n",
    "model.load_state_dict(torch.load(f\"checkpoints/{exp_name}/best.ckpt\", map_location=\"cpu\"))\n",
    "\n",
    "model.eval()\n",
    "loss, acc = evaluating(model, eval_dl, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\\naccuracy: {acc:.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 推理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T08:20:00.806997Z",
     "iopub.status.busy": "2025-01-21T08:20:00.806739Z",
     "iopub.status.idle": "2025-01-21T08:21:58.270987Z",
     "shell.execute_reply": "2025-01-21T08:21:58.270499Z",
     "shell.execute_reply.started": "2025-01-21T08:20:00.806974Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 4688/4688 [01:57<00:00, 39.92it/s]\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>filepath</th>\n",
       "      <th>class</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>competitions/cifar-10/test/1.png</td>\n",
       "      <td>dog</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>competitions/cifar-10/test/2.png</td>\n",
       "      <td>airplane</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>competitions/cifar-10/test/3.png</td>\n",
       "      <td>automobile</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>competitions/cifar-10/test/4.png</td>\n",
       "      <td>airplane</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>competitions/cifar-10/test/5.png</td>\n",
       "      <td>airplane</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                           filepath       class\n",
       "0  competitions/cifar-10/test/1.png         dog\n",
       "1  competitions/cifar-10/test/2.png    airplane\n",
       "2  competitions/cifar-10/test/3.png  automobile\n",
       "3  competitions/cifar-10/test/4.png    airplane\n",
       "4  competitions/cifar-10/test/5.png    airplane"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# test_df\n",
    "test_ds = Cifar10Dataset(\"test\", transform=transforms_eval)\n",
    "test_dl = DataLoader(test_ds, batch_size=batch_size, shuffle=False, drop_last=False)\n",
    "\n",
    "preds_collect = []\n",
    "model.eval()\n",
    "for data, fake_label in tqdm(test_dl):\n",
    "    data = data.to(device=device)\n",
    "    logits = model(data)\n",
    "    preds = [test_ds.idx_to_label[idx] for idx in logits.argmax(axis=-1).cpu().tolist()]\n",
    "    preds_collect.extend(preds)\n",
    "    \n",
    "test_df[\"class\"] = preds_collect\n",
    "test_df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T08:21:58.272012Z",
     "iopub.status.busy": "2025-01-21T08:21:58.271588Z",
     "iopub.status.idle": "2025-01-21T08:21:58.590533Z",
     "shell.execute_reply": "2025-01-21T08:21:58.589993Z",
     "shell.execute_reply.started": "2025-01-21T08:21:58.271992Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 导出 submission.csv\n",
    "test_df.to_csv(\"submission.csv\", index=False)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
